
package interrupt;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import interrupt.*;

import static java.lang.Thread.sleep;

public class TEC {
    private static InstructionList IL;
    
    private Memory memory;       //�ڴ�
    private Register[] Reg;       //�Ĵ���, 16λboolean
    @SuppressWarnings("unused")
	private ALU ALU;         //������, 16λboolean
    private short AR;               //��ַ�Ĵ���, int
    private Register PORT_80;
    private Register PORT_81;
    private boolean InterruptEnabled;
    private byte InterruptLevel;
    private boolean ToEnd;
    private boolean SignIn;
    private boolean SignOut;
    private final byte pSP = 4;
    private final byte pPC = 5;
    public LinkedList<Byte> interrupts = new LinkedList<Byte>();
    
    public TEC(){
        this.Reg = new Register[16];
        memory = new Memory();
        IL = new InstructionList();
        this.init();
    }
    
    public final void init(){
        for(int i = 0; i < this.Reg.length; i++){
            Reg[i] = new Register();
        }
        this.PORT_80 = new Register();
        this.PORT_81 = new Register();
        this.SignIn = false;
        this.SignOut = false;
        this.ALU = new ALU();
        this.setSP(0x2780);
        this.ToEnd = true;
        this.InterruptEnabled = false;
        this.InterruptLevel = -1;
    }
    
    public String[] run() throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InterruptedException{
        String[] result = new String[Reg.length];
        this.ToEnd = false;
        while(!ToEnd){
            while(!interrupts.isEmpty()){
                this.Interrupt((byte) interrupts.removeFirst());
            }
            ExecuteAnInstruction();
            sleep(10);
        }
        
        for(int i = 0; i < Reg.length; i++){
            result[i] = Reg[i].toString().toUpperCase();
        }
        return result;
    }
    
    public void ExecuteAnInstruction() throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        this.AR = this.getPC();
        
        //System.out.print(Integer.toHexString(AR) + ": ");
        byte ope = this.memory.getOPE(AR); 
        
        this.setPC((short) (AR+IL.getSize(ope)));
        
        String Function = IL.getFunc(ope);
        
//        System.out.println(Function);
        
        Method Func = this.getClass().getDeclaredMethod(Function);
        
        Func.invoke(this);
    }
    
    
    public boolean Input(String ins, String type){
        switch(type){
            case "ModeA": {
                return ExecuteA(ins);
            }
            case "ModeE": {
                return ExecuteE(ins);
            }
        }
        return false;
    }
    
    private boolean ExecuteA(String ins){
        if(!IL.Competent(ins)){
            return false;
        }
        AR = getPC();
        String[] inss = ins.split(" |,");
        short ORDER = IL.getCode(inss[0]);
        ORDER = (short) (ORDER << 24 >>> 16);
        switch(IL.getType(inss[0])){
            case "DRSR": {//DRSR  12
                ORDER += Tools.getRegnum(inss[1]) << 4;
                ORDER += Tools.getRegnum(inss[2]);
                break;
            }
            case "DR": {//DR  9
                ORDER += Tools.getRegnum(inss[1]) << 4;
                break;
            }
            case "SR": {//SR  3
                ORDER += Tools.getRegnum(inss[1]);
                break;
                
            }
            case "OFFSET": {//OFFSET  7
                ORDER += Tools.getOffset(AR, inss[1]) <<24 >>> 24;
                break;
            }
            case "IO": { //I/O  2
                ORDER += Tools.getRegnum("R"+inss[1].substring(0, 1)) << 4;
                ORDER += Tools.getRegnum("R"+inss[1].substring(1));
                break;
            }
            default: { // 8
                break;
            }
        }
        this.memory.setMemory(AR, ORDER);
        if(IL.getSize(inss[0]) == 2){
            short DATA = 0;
            switch(inss[0]){  //7
                case "JMPA": case "CALA": {
                    DATA = Integer.valueOf(inss[1], 16).shortValue();
                    break;
                }
                case "MVRD": case "LDRA": {
                    ORDER += Tools.getRegnum(inss[1]) << 4;
                    DATA = Integer.valueOf(inss[2], 16).shortValue();
                    break;
                }
                case "LDRX": case "STRX": {
                    //
                    break;
                }
                case "STRA": {
                    ORDER += Tools.getRegnum(inss[2]);
                    DATA = Integer.valueOf(inss[1], 16).shortValue();
                    break;
                }
            }
            this.memory.setMemory(AR, ORDER);
            this.memory.setMemory((short) (AR+1), DATA);
        }
        this.setPC((short) (AR+IL.getSize(inss[0])));
        
        return true;
    }
    
    private boolean ExecuteE(String ins){
        AR = getPC();
        short result = Integer.valueOf(ins, 16).shortValue();
        this.memory.setMemory(AR++, result);
        this.setPC(AR);
        
        return true;
    }
    
    /*@SuppressWarnings("unused")
	private void ExecuteT(String ins){
        String ADR = ins.split(" ")[1];
    }*/
    
    synchronized public void setPC(short DATA){
        this.Reg[pPC].setReg(Integer.toHexString(DATA));
    }
    
    synchronized public void setSP(int DATA){
        this.Reg[pSP].setReg(Integer.toHexString(DATA));
    }
    
    public short getPC(){
        String PC = Reg[pPC].toString();
        short pc = Short.parseShort(PC, 16);
        return pc;
    }
    
    private short getSP(){
        String SP = Reg[pSP].toString();
        short sp = Short.parseShort(SP, 16);
        return sp;
    }
    
    private void push(short DATA){
        short sp = getSP();
        memory.setMemory(sp--, DATA);
        setSP(sp);
//        System.out.println(Integer.toHexString(AR).toUpperCase() + " PUSH:" + Integer.toHexString(sp+1).toUpperCase() + " " + Tools.getADR(DATA).toUpperCase());
    }
    
    private short pop(){
        short sp = getSP();
        short result = memory.getMemory(++sp);
        setSP(sp);
//        System.out.println(Integer.toHexString(AR).toUpperCase() + " POP: " + Integer.toHexString(sp).toUpperCase() + " " + Tools.getADR(result).toUpperCase());
        return result;
    }
    
    private void ExecuteADD() {
        byte DR = memory.getDR(AR);
        byte SR = memory.getSR(AR);
        this.ALU.setReg(this.Reg[DR].getReg());
        this.ALU.ADD(this.Reg[SR].getReg());
        this.Reg[DR].setReg(this.ALU.getReg());
    }
    
    private void ExecuteSUB() {
        byte DR = memory.getDR(AR);
        byte SR = memory.getSR(AR);
        this.ALU.setReg(Reg[DR].getReg());
        this.ALU.SUB(Reg[SR].getReg());
        this.Reg[DR].setReg(this.ALU.getReg());
    }
    

    private void ExecuteAND() {
        byte DR = memory.getDR(AR);
        byte SR = memory.getSR(AR);
        this.ALU.setReg(this.Reg[DR].getReg());
        this.ALU.AND(this.Reg[SR].getReg());
        this.Reg[DR].setReg(this.ALU.getReg());
    }
    
    private void ExecuteCMP() {
        byte DR = memory.getDR(AR);
        byte SR = memory.getSR(AR);
        this.ALU.setReg(this.Reg[DR].getReg());
        this.ALU.SUB(this.Reg[SR].getReg());
    }
    
    private void ExecuteXOR() {
        byte DR = memory.getDR(AR);
        byte SR = memory.getSR(AR);
        this.ALU.setReg(this.Reg[DR].getReg());
        this.ALU.XOR(this.Reg[SR].getReg());
        this.Reg[DR].setReg(this.ALU.getReg());
    }
    
    private void ExecuteTEST() {
        byte DR = memory.getDR(AR);
        byte SR = memory.getSR(AR);
        this.ALU.setReg(this.Reg[DR].getReg());
        this.ALU.AND(this.Reg[SR].getReg());
    }
    
    private void ExecuteOR() {
        byte DR = memory.getDR(AR);
        byte SR = memory.getSR(AR);
        this.ALU.setReg(this.Reg[DR].getReg());
        this.ALU.OR(this.Reg[SR].getReg());
        this.Reg[DR].setReg(this.ALU.getReg());
    }
    
    private void ExecuteMVRR() {
        byte DR = memory.getDR(AR);
        byte SR = memory.getSR(AR);
        this.Reg[DR].setReg(this.Reg[SR].getReg());
    }
    
    private void ExecuteDEC() {
        byte DR = memory.getDR(AR);
        this.ALU.setReg(this.Reg[DR].getReg());
        this.ALU.SUB((short) 1);
        this.Reg[DR].setReg(this.ALU.getReg());
    }
    
    private void ExecuteINC() {
        byte DR = memory.getDR(AR);
        this.ALU.setReg((short)this.Reg[DR].getReg());
        this.ALU.ADD((short)1);
        this.Reg[DR].setReg(this.ALU.getReg());
    }
    
    private void ExecuteSHL() {
        byte DR = memory.getDR(AR);
        this.ALU.setReg(this.Reg[DR].getReg());
        this.ALU.MOV_R(false);
        this.Reg[DR].setReg(this.ALU.getReg());
    }
    
    private void ExecuteSHR() {
        byte DR = memory.getDR(AR);
        this.ALU.setReg(this.Reg[DR].getReg());
        this.ALU.MOV_L(false);
        this.Reg[DR].setReg(this.ALU.getReg());
    }
    
    private void ExecuteJR() {
        byte offset = this.memory.getOffset(AR);
        this.AR -= offset;
        this.setPC(AR);
    }
    
    private void ExecuteJRC() {
        if(this.ALU.getC()){
            ExecuteJR();
        }
        this.ALU.setC(false);
    }
    
    private void ExecuteJRNC() {
        if(!this.ALU.getC()){
            ExecuteJR();
        }
        this.ALU.setC(false);
    }
    
    private void ExecuteJRZ() {
        if(this.ALU.getZ()){
            ExecuteJR();
        }
        this.ALU.setZ(false);
    }
    
    private void ExecuteJRNZ() {
        if(!this.ALU.getZ()){
            ExecuteJR();
        }
        this.ALU.setZ(false);
    }
    
    private void ExecuteJMPA() {
        short pc = this.memory.getMemory((short) (AR+1));
        this.setPC(pc);
    }
    
    private void ExecuteLDRR() {
    }
    
    private void ExecuteIN() {
        int port = 16*this.memory.getDR(AR) + this.memory.getSR(AR);
        if(port == 0x80){
            Reg[0].setReg(PORT_80.getReg());
            PORT_81.setBit((byte)2, false);
        } else if(port == 0x81){
            if(SignIn) {
                Reg[0].setReg(PORT_81.getReg());
            } else {
                SignIn = true;
            }
        }
    }
    
    private void ExecuteSTRR() {
    }
    
    private void ExecutePSHF() {
    }
    
    private void ExecutePUSH() {
        byte SR = this.memory.getSR(AR);
//        System.out.println("PUSH");
//        System.out.println("AR:"+AR);
//        System.out.println("DR:"+SR);
//        System.out.println(Reg[SR].getReg());
        this.push(Reg[SR].getReg());

    }
    
    private void ExecuteOUT() {
        int port = 16*this.memory.getDR(AR) + this.memory.getSR(AR);
        if(port == 0x80){
            PORT_80.setReg(Reg[0].getReg());
            SignOut = true;
        } else {
            PORT_81.setReg(Reg[0].getReg());
        }
    }
    
    private void ExecutePOP() {
        byte DR = this.memory.getDR(AR);
//        System.out.println("POP");
//        System.out.println("AR:"+AR);
//        System.out.println("SR:"+DR);
        Reg[DR].setReg(this.pop());
//        System.out.println(Reg[DR].getReg());
    }
    
    private void ExecuteMVRD() {
        byte DR = this.memory.getDR(AR);
        Reg[DR].setReg(this.memory.getMemory((short) (AR+1)));
    }
    
    private void ExecutePOPF() {
    }
    
    private void ExecuteRET() {
        if(this.getSP() == 0x2780){
            this.ToEnd = true;
        }
        else{
            short ADR = this.pop();
            short pc;
                pc = ADR;
            this.setPC(pc);
        }
    }
    
    private void ExecuteCALA() {
        this.push((short) (AR+2));
        this.setPC(this.memory.getMemory((short) (AR+1)));
    }
    
    private void ExecuteADC() {
    }
    
    private void ExecuteSBB() {
    }
    
    private void ExecuteRCL() {
    }
    
    private void ExecuteRCR() {
    }
    
    private void ExecuteASR() {
    }
    
    private void ExecuteNOT() {
    }
    
    private void ExecuteJMPR() {
    }
    
    private void ExecuteJRS() {
        if(ALU.getS()){
            ExecuteJR();
        }
        this.ALU.setS(false);
    }
    
    private void ExecuteJRNS() {
        if(!ALU.getS()){
            ExecuteJR();
        }
        this.ALU.setS(false);
    }
    
    private void ExecuteCLC() {
        ALU.setC(true);
    }
    
    private void ExecuteSTC() {
        ALU.setC(false);
    }
    
    private void ExecuteEI() {
        InterruptEnabled = true;
    }
    
    private void ExecuteDI() {
        InterruptEnabled = false;
    }
    
    private void ExecuteCALR() {
    }
    
    private void ExecuteLDRA() {
    }
    
    private void ExecuteLDRX() {
    }
    
    private void ExecuteSTRX() {
    }
    
    private void ExecuteSTRA() {
    }
    
    private void ExecuteIRET() {
        short pc = this.pop();
        this.setPC(pc);
        this.InterruptLevel--;
        
    }
    
    public boolean getSignIn(){
        return this.SignIn;
    }
    
    public boolean getSignOut(){
        boolean t = this.SignOut;
        this.SignOut = false;
        PORT_81.setBit((byte)1, true);
        return t;
    }
    
    synchronized public void setIOPORT(short data){
        PORT_81.setBit((byte)2, true);
        PORT_80.setReg(data);
        this.SignIn = false;
    }
    
    public String getIOPORT(){
        short data = PORT_80.getReg();
        return String.valueOf((char) data);
    }
    
    public boolean getToEnd(){
        return this.ToEnd;
    }
    
    private void Interrupt(byte num){
        if(!this.InterruptEnabled)
            return;
        if(num < this.InterruptLevel)
            return;
        short pc = getPC();
        push(pc);
        InterruptLevel = num;
        setPC((short) (0x2404 + num*4));
    }
    
    public String getOrders(String ADR){
        short adr = Short.parseShort(ADR, 16);
        StringBuilder result = new StringBuilder("");
        for(short i = 0; i < 12; i++){
            short ope = memory.getOPE((short) (adr+i));
            result = new StringBuilder(result.toString()+ Tools.fill(Integer.toHexString(adr+i)+": "));
            result = new StringBuilder(result.toString()+ Tools.align(Tools.getADR(memory.getMemory((short) (adr+i))))+"      ");
            result = new StringBuilder(result.toString()+ Tools.align(IL.getName(ope)));
            result = new StringBuilder(result.toString()+"   ");
            switch(IL.getType(IL.getName(ope))){
                case "DRSR":{
                    result = new StringBuilder(result.toString()+"R"+Byte.toString(memory.getDR((short) (adr+i)))+",    "+"R"+Byte.toString(memory.getSR((short) (adr+i))));
                    break;
                }
                case "DR":{
                    result = new StringBuilder(result.toString()+"R"+Byte.toString(memory.getDR((short) (adr+i))));
                    break;
                }
                case "SR":{
                    result = new StringBuilder(result.toString()+"    "+",    "+Byte.toString(memory.getSR((short) (adr+i))));
                    break;
                }
                case "OFFSET":{
                    result.append(Integer.toHexString(memory.getOffset((short) (adr+i)) + adr+i));
                    break;
                }
                case "IO":{
                    result.append(Tools.align(Integer.toHexString(memory.getDR((short) (adr+i))*16+memory.getSR((short) (adr+i)))));
                    break;
                }
                case "ADR":{
                    result.append(Tools.align(Tools.getADR(memory.getMemory((short) (adr+i+1)))));
                    break;
                    
                }
                case "DRADR":{
                	result.append("R").append(memory.getDR((short) (adr+i))).append(",    ")
                        .append(Tools.getADR(memory.getMemory((short) (adr+i+1))));
                    break;
                }
                case "SRADR":{
                    result.append("    ").append(",    ").append("R").append(memory.getSR((short) (adr+i)))
                          .append(",    ").append(Tools.getADR(memory.getMemory((short) (adr+i+1))));
                    break;
                }
                case "DRSRADR":{
                result.append("R").append(memory.getDR((short) (adr+i))).append(",    ").append("R")
                        .append(memory.getSR((short) (adr+i))).append(",    ")
                        .append(Tools.getADR(memory.getMemory((short) (adr+i+1))));
                    break;
                }
            }
            result.append("\n");
            if(IL.getSize(IL.getName(ope)) == 2){
                i++;
            }
        }
        return result.toString().toUpperCase();
    }
    
    public void test(){
        this.setPC((short)0x2000);
        this.ExecuteA("EI");
        this.ExecuteA("MVRD R0,4D");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("IN 81");
        this.ExecuteA("SHR R0");
        this.ExecuteA("SHR R0");
        this.ExecuteA("JRNC 2001");
        this.ExecuteA("IN 80");
        this.ExecuteA("RET");
        
        this.setPC((short)0x2200);
        this.ExecuteA("PUSH R0");
        this.ExecuteA("IN 81");
        this.ExecuteA("SHR R0");
        this.ExecuteA("JRNC 2201");
        this.ExecuteA("POP R0");
        this.ExecuteA("OUT 80");
        this.ExecuteA("RET");
        
        this.setPC((short)0x2420);
        this.ExecuteA("PUSH R0");
        this.ExecuteA("MVRD R0,31");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("IN 81");
        this.ExecuteA("SHR R0");
        this.ExecuteA("SHR R0");
        this.ExecuteA("JRNC 2421");
        this.ExecuteA("IN 80");
        this.ExecuteA("POP R0");
        this.ExecuteA("IRET");
        
        this.setPC((short)0x2430);
        this.ExecuteA("PUSH R0");
        this.ExecuteA("MVRD R0,32");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("IN 81");
        this.ExecuteA("SHR R0");
        this.ExecuteA("SHR R0");
        this.ExecuteA("JRNC 2431");
        this.ExecuteA("IN 80");
        this.ExecuteA("POP R0");
        this.ExecuteA("IRET");
        
        this.setPC((short)0x2440);
        this.ExecuteA("PUSH R0");
        this.ExecuteA("MVRD R0,33");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("IN 81");
        this.ExecuteA("SHR R0");
        this.ExecuteA("SHR R0");
        this.ExecuteA("JRNC 2441");
        this.ExecuteA("IN 80");
        this.ExecuteA("POP R0");
        this.ExecuteA("IRET");
        
        this.setPC((short)0x2404);
        this.ExecuteA("JR 2420");
        this.setPC((short)0x2408);
        this.ExecuteA("JR 2430");
        this.setPC((short)0x240C);
        this.ExecuteA("JR 2440");
    }

    public void test3(){
        this.setPC((short)0x2000);
        this.ExecuteA("PUSH R0");
        this.ExecuteA("PUSH R3");
        this.ExecuteA("PUSH R4");
        this.ExecuteA("PUSH R5");
        this.ExecuteA("POP R0");
        this.ExecuteA("POP R3");
        this.ExecuteA("POP R4");
        this.ExecuteA("POP R5");
    }

    public void test2(){
        this.setPC((short)0x2000);
        this.ExecuteA("EI");
        this.ExecuteA("MVRD R0,36");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("JR 2001");
        this.ExecuteA("RET");

        this.setPC((short)0x2420);
        this.ExecuteA("PUSH R0");
        this.ExecuteA("PUSH R3");
        this.ExecuteA("MVRD R3,31");
        this.ExecuteA("JR 2450");

        this.setPC((short)0x2430);
        this.ExecuteA("PUSH R0");
        this.ExecuteA("PUSH R3");
        this.ExecuteA("MVRD R3,32");
        this.ExecuteA("JR 2450");

        this.setPC((short)0x2440);
        this.ExecuteA("PUSH R0");
        this.ExecuteA("PUSH R3");
        this.ExecuteA("MVRD R3,33");
        this.ExecuteA("JR 2450");

        this.setPC((short)0x2450);
        this.ExecuteA("EI");
        this.ExecuteA("MVRD R0,42");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("MVRD R0,49");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("MVRR R0,R3");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("IN 81");
        this.ExecuteA("SHR R0");
        this.ExecuteA("SHR R0");
        this.ExecuteA("JRNC 245C");
        this.ExecuteA("IN 80");
        this.ExecuteA("MVRD R0,45");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("MVRD R0,49");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("MVRR R0,R3");
        this.ExecuteA("CALA 2200");
        this.ExecuteA("POP R3");
        this.ExecuteA("POP R0");
        this.ExecuteA("IRET");

        this.setPC((short)0x2200);
        this.ExecuteA("PUSH R0");
        this.ExecuteA("IN 81");
        this.ExecuteA("SHR R0");
        this.ExecuteA("JRNC 2201");
        this.ExecuteA("POP R0");
        this.ExecuteA("OUT 80");
        this.ExecuteA("RET");

        this.setPC((short)0x2404);
        this.ExecuteA("JR 2420");
        this.setPC((short)0x2408);
        this.ExecuteA("JR 2430");
        this.setPC((short)0x240C);
        this.ExecuteA("JR 2440");
    }
}